﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Web.UI;
using System.Collections.Specialized;
using System.Text.RegularExpressions;
using System.Xml;
using System.Globalization;

namespace Lilium.Web.Mvc.AjaxControlToolkit
{
	/// <summary>
	/// CascadingDropDown extender class definition
	/// </summary>
	[RequiredResource(typeof(CommonToolkitScripts))]
	[ToolkitScriptReference("CascadingDropDown.CascadingDropDownBehavior.js", LoadOrder = 1)]
	public class CascadingDropDownExtender: ToolkitExtenderBase
	{
		public CascadingDropDownExtender(string elementID)
			: base("AjaxControlToolkit.CascadingDropDownBehavior", elementID)
		{
			EnableClientState = true;
		}

		public static object CreateCascadingDropDownList(IEnumerable<CascadingDropDownNameValue> items)
		{
			if (items == null)
				throw new ArgumentNullException("items");
			return new
			{
				d = items.ToArray(),
			};
		}

		public static object CreateCascadingDropDownList(XmlDocument document, string[] documentHierarchy, StringDictionary knownCategoryValuesDictionary, string category, Regex inputValidationRegex)
		{
			return CreateCascadingDropDownList(QuerySimpleCascadingDropDownDocument(document, documentHierarchy, knownCategoryValuesDictionary, category, inputValidationRegex));
		}

		/// <summary>
		/// Helper method to parse the private storage format used to communicate known category/value pairs
		/// </summary>
		/// <param name="knownCategoryValues">private storage format string</param>
		/// <returns>dictionary of category/value pairs</returns>
		[SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "Avoiding possible breaking change")]
		public static StringDictionary ParseKnownCategoryValuesString(string knownCategoryValues)
		{
			// Validate parameters
			if (null == knownCategoryValues)
			{
				throw new ArgumentNullException("knownCategoryValues");
			}

			StringDictionary dictionary = new StringDictionary();
			if (null != knownCategoryValues)
			{
				// Split into category/value pairs
				foreach (string knownCategoryValue in knownCategoryValues.Split(';'))
				{
					// Split into category and value
					string[] knownCategoryValuePair = knownCategoryValue.Split(':');
					if (2 == knownCategoryValuePair.Length)
					{
						// Add the pair to the dictionary
						dictionary.Add(knownCategoryValuePair[0].ToLowerInvariant(), knownCategoryValuePair[1]);
					}
				}
			}
			return dictionary;
		}

		/// <summary>
		/// Helper method to provide a simple implementation of a method to query a data set and return the relevant drop down contents
		/// </summary>
		/// <param name="document">XML document containing the data set</param>
		/// <param name="documentHierarchy">list of strings representing the hierarchy of the data set</param>
		/// <param name="knownCategoryValuesDictionary">known category/value pairs</param>
		/// <param name="category">category for which the drop down contents are desired</param>
		/// <returns>contents of the specified drop down subject to the choices already made</returns>
		[SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes", Justification = "Non-IXPathNavigable members of XmlDocument are used")]
		public static CascadingDropDownNameValue[] QuerySimpleCascadingDropDownDocument(XmlDocument document, string[] documentHierarchy, StringDictionary knownCategoryValuesDictionary, string category)
		{
			// Use a default Regex for input validation that excludes any user input with the characters
			// '/', ''', or '*' in an effort to prevent XPath injection attacks against the web service.
			// Public sites should use a more restrictive Regex that is customized for their own data.
			return QuerySimpleCascadingDropDownDocument(document, documentHierarchy, knownCategoryValuesDictionary, category, new Regex("^[^/'\\*]*$"));
		}

		/// <summary>
		/// Helper method to provide a simple implementation of a method to query a data set and return the relevant drop down contents
		/// </summary>
		/// <param name="document">XML document containing the data set</param>
		/// <param name="documentHierarchy">list of strings representing the hierarchy of the data set</param>
		/// <param name="knownCategoryValuesDictionary">known category/value pairs</param>
		/// <param name="category">category for which the drop down contents are desired</param>
		/// <param name="inputValidationRegex">regular expression used to validate user input to the web service (to prevent XPath injection attacks)</param>
		/// <returns>contents of the specified drop down subject to the choices already made</returns>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes", Justification = "Non-IXPathNavigable members of XmlDocument are used")]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "Avoiding possible breaking change")]
		public static CascadingDropDownNameValue[] QuerySimpleCascadingDropDownDocument(XmlDocument document, string[] documentHierarchy, StringDictionary knownCategoryValuesDictionary, string category, Regex inputValidationRegex)
		{
			// Validate parameters
			if (null == document)
			{
				throw new ArgumentNullException("document");
			}
			if (null == documentHierarchy)
			{
				throw new ArgumentNullException("documentHierarchy");
			}
			if (null == knownCategoryValuesDictionary)
			{
				throw new ArgumentNullException("knownCategoryValuesDictionary");
			}
			if (null == category)
			{
				throw new ArgumentNullException("category");
			}
			if (null == inputValidationRegex)
			{
				throw new ArgumentNullException("inputValidationRegex");
			}

			// Validate input
			foreach (string key in knownCategoryValuesDictionary.Keys)
			{
				if (!inputValidationRegex.IsMatch(key) || !inputValidationRegex.IsMatch(knownCategoryValuesDictionary[key]))
				{
					throw new ArgumentException("Invalid characters present.", "category");
				}
			}
			if (!inputValidationRegex.IsMatch(category))
			{
				throw new ArgumentException("Invalid characters present.", "category");
			}

			// Root the XPath query
			string xpath = "/" + document.DocumentElement.Name;

			// Build an XPath query into the data set to select the relevant items
			foreach (string key in documentHierarchy)
			{
				if (knownCategoryValuesDictionary.ContainsKey(key))
				{
					xpath += string.Format(CultureInfo.InvariantCulture, "/{0}[(@name and @value='{1}') or (@name='{1}' and not(@value))]", key, knownCategoryValuesDictionary[key]);
				}
			}
			xpath += ("/" + category.ToLowerInvariant());

			// Perform the XPath query and add the results to the list
			List<CascadingDropDownNameValue> result = new List<CascadingDropDownNameValue>();
			foreach (XmlNode node in document.SelectNodes(xpath))
			{
				string name = node.Attributes.GetNamedItem("name").Value;
				XmlNode valueNode = node.Attributes.GetNamedItem("value");
				string value = ((null != valueNode) ? valueNode.Value : name);
				XmlNode defaultNode = node.Attributes.GetNamedItem("default");
				bool defaultValue = ((null != defaultNode) ? bool.Parse(defaultNode.Value) : false);
				result.Add(new CascadingDropDownNameValue(name, value, defaultValue));
			}

			// Return the list
			return result.ToArray();
		}

		#region public string ParentControlID

		private string m_ParentControlID;


		/// <summary>
		/// Optional ID of the parent DropDownList (upon which the contents of this control are based)
		/// </summary>
		//[IDReferenceProperty(typeof(DropDownList))]
		[DefaultValue("")]
		[SuppressMessage("Microsoft.Naming", "CA1706:ShortAcronymsShouldBeUppercase", Justification = "Following ASP.NET AJAX pattern")]
		[ExtenderControlProperty()]
		public string ParentControlID { [DebuggerNonUserCodeAttribute] get { return m_ParentControlID ?? ""; } [DebuggerNonUserCodeAttribute] set { m_ParentControlID = value; } }

		#endregion

		#region public string Category

		private string m_Category;

		/// <summary>
		/// Category of this DropDownList (used when communicating with the helper web service)
		/// </summary>
		[RequiredProperty()]
		[DefaultValue("")]
		[ExtenderControlProperty()]
		public string Category { [DebuggerNonUserCodeAttribute] get { return m_Category ?? ""; } [DebuggerNonUserCodeAttribute] set { m_Category = value; } }

		#endregion

		#region public string PromptText

		private string m_PromptText;

		/// <summary>
		/// Optional text displayed by a DropDownList the user has not yet touched.
		/// If omitted, first item in the dropdown is selected
		/// </summary>
		[DefaultValue("")]
		[ExtenderControlProperty()]
		public string PromptText { [DebuggerNonUserCodeAttribute] get { return m_PromptText ?? ""; } [DebuggerNonUserCodeAttribute] set { m_PromptText = value; } }

		#endregion

		#region public string PromptValue

		private string m_PromptValue;

		/// <summary>
		/// Optional value for the option displayed by a DropDownList showing the PromptText
		/// </summary>
		[DefaultValue("")]
		[ExtenderControlProperty()]
		public string PromptValue { [DebuggerNonUserCodeAttribute] get { return m_PromptValue ?? ""; } [DebuggerNonUserCodeAttribute] set { m_PromptValue = value; } }

		#endregion

		#region public string EmptyText

		private string m_EmptyText;

		/// <summary>
		/// Optional text for the option displayed when the list is empty
		/// </summary>
		[DefaultValue("")]
		[ExtenderControlProperty()]
		public string EmptyText { [DebuggerNonUserCodeAttribute] get { return m_EmptyText ?? ""; } [DebuggerNonUserCodeAttribute] set { m_EmptyText = value; } }

		#endregion

		#region public string EmptyValue

		private string m_EmptyValue;

		/// <summary>
		/// Optional value for the option displayed when the list is empty
		/// </summary>
		[DefaultValue("")]
		[ExtenderControlProperty()]
		public string EmptyValue { [DebuggerNonUserCodeAttribute] get { return m_EmptyValue ?? ""; } [DebuggerNonUserCodeAttribute] set { m_EmptyValue = value; } }

		#endregion

		#region public string LoadingText

		private string m_LoadingText;

		/// <summary>
		/// Optional text displayed by a DropDownList when it is loading its data
		/// </summary>
		[DefaultValue("")]
		[ExtenderControlProperty()]
		public string LoadingText { [DebuggerNonUserCodeAttribute] get { return m_LoadingText ?? ""; } [DebuggerNonUserCodeAttribute] set { m_LoadingText = value; } }

		#endregion

		#region public string SelectedValue

		private string m_SelectedValue;

		/// <summary>
		/// Selected value of the drop down
		/// </summary>
		[DefaultValue("")]
		[ExtenderControlProperty()]
		public string SelectedValue { [DebuggerNonUserCodeAttribute] get { return m_SelectedValue ?? ""; } [DebuggerNonUserCodeAttribute] set { m_SelectedValue = value; } }

		#endregion

		#region public string ServicePath

		private string m_ServicePath;

		/// <summary>
		/// Path to the helper web service
		/// </summary>        
		[UrlProperty()]
		[ExtenderControlProperty()]
		[TypeConverter(typeof(ServicePathConverter))]
		public string ServicePath { [DebuggerNonUserCodeAttribute] get { return m_ServicePath ?? ""; } [DebuggerNonUserCodeAttribute] set { m_ServicePath = value; } }

		/// <summary>
		/// Prevent the service path from being serialized when it's empty
		/// </summary>
		/// <returns>Whether the service path should be serialized</returns>
		private bool ShouldSerializeServicePath()
		{
			return !string.IsNullOrEmpty(ServiceMethod);
		}

		#endregion

		#region public string ServiceMethod

		private string m_ServiceMethod;

		/// <summary>
		/// Name of the web service method
		/// </summary>
		[RequiredProperty()]
		[DefaultValue("")]
		[ExtenderControlProperty()]
		public string ServiceMethod { [DebuggerNonUserCodeAttribute] get { return m_ServiceMethod ?? ""; } [DebuggerNonUserCodeAttribute] set { m_ServiceMethod = value; } }

		#endregion

		#region public string ContextKey

		private string m_ContextKey;

		/// <summary>
		/// User/page specific context provided to an optional overload of the
		/// web method described by ServiceMethod/ServicePath.  If the context
		/// key is used, it should have the same signature with an additional
		/// parameter named contextKey of type string.
		/// </summary>
		[ExtenderControlProperty]
		[ClientPropertyName("contextKey")]
		[DefaultValue(null)]
		public string ContextKey { [DebuggerNonUserCodeAttribute] get { return m_ContextKey; } [DebuggerNonUserCodeAttribute] set { m_ContextKey = value; } }

		#endregion

		#region public bool UseContextKey

		private bool m_UseContextKey;

		/// <summary>
		/// Whether or not the ContextKey property should be used.  This will be
		/// automatically enabled if the ContextKey property is ever set
		/// (on either the client or the server).  If the context key is used,
		/// it should have the same signature with an additional parameter
		/// named contextKey of type string.
		/// </summary>
		[ExtenderControlProperty]
		[ClientPropertyName("useContextKey")]
		[DefaultValue(false)]
		public bool UseContextKey { [DebuggerNonUserCodeAttribute] get { return m_UseContextKey; } [DebuggerNonUserCodeAttribute] set { m_UseContextKey = value; } }

		#endregion

		/* Целевого контрола не существует, но можно создать методы для извлечения VS
		/// <summary>
		/// Populate DropDownLists with their SelectedValues
		/// </summary>
		private void CascadingDropDown_ClientStateValuesLoaded(object sender, EventArgs e)
		{
			DropDownList dropDownList = (DropDownList)TargetControl;
			dropDownList.Items.Clear();
			string separator = ":::";
			string clientState = base.ClientState;
			int separatorIndex = (clientState ?? "").IndexOf(separator, StringComparison.Ordinal);
			if (-1 == separatorIndex)
			{
				// ClientState is the value to set
				dropDownList.Items.Add(clientState);
			}
			else
			{
				// Parse the value/text out of ClientState and set them
				string value = clientState.Substring(0, separatorIndex);
				string text = clientState.Substring(separatorIndex + separator.Length);
				dropDownList.Items.Add(new ListItem(text, value));
			}
		}
		*/
	}
}
